package MeteoCal.bean;

import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.context.FacesContext;
import MeteoCal.entity.Event;
import MeteoCal.manager.EventManager;
import MeteoCal.manager.UserManager;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.context.ConversationScoped;

import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
import javax.faces.bean.SessionScoped;

import net.fortuna.ical4j.data.*;
import net.fortuna.ical4j.model.Calendar;
import net.fortuna.ical4j.model.Component;
import net.fortuna.ical4j.model.Property;
import net.fortuna.ical4j.model.ValidationException;
import net.fortuna.ical4j.model.component.VEvent;
import net.fortuna.ical4j.model.parameter.Value;
import net.fortuna.ical4j.model.property.CalScale;
import net.fortuna.ical4j.model.property.Description;
import net.fortuna.ical4j.model.property.Location;
import net.fortuna.ical4j.model.property.ProdId;
import net.fortuna.ical4j.model.property.Uid;
import net.fortuna.ical4j.model.property.Version;
import net.fortuna.ical4j.util.UidGenerator;
import org.apache.log4j.BasicConfigurator;
 
import org.primefaces.event.SelectEvent;
import org.primefaces.model.DefaultScheduleEvent;
import org.primefaces.model.DefaultScheduleModel;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.ScheduleEvent;
import org.primefaces.model.ScheduleModel;
import org.primefaces.model.StreamedContent;
import org.primefaces.model.UploadedFile;
 
/**
*
* @authors Guttadoro, Iommazzo, Saullo
*/
@ManagedBean
@SessionScoped
public class ScheduleBean implements Serializable {
 
    private Collection eventsDay;
    //private ScheduleModel eventModel;
    private ScheduleEvent event = new DefaultScheduleEvent();
    private String pathExport;
    private String pathImport;
    private String contentType;
    private UploadedFile fileUploaded;
    private List<Event> existingUploadedEventsOverlap; 
    
    @EJB
    private UserManager um;
    
    @EJB
    private EventManager em;

    @ManagedProperty("#{scheduleUserBean}")
    private ScheduleUserBean service;
    
    @ManagedProperty("#{eventBean}")
    private EventBean bean;
    
//    @PostConstruct
//    public void init() {
//        
//        //Obtain all events of the user
//        Collection events = em.getAllEvents(um.getLoggedUser());
//        System.out.println("Eventi-------zzzz" + events.toString());
//        //Add the events to eventModel
//        eventModel = addEventToEventModel(events); 
//    }

    public Collection getEventsDay() {
        return eventsDay;
    }

    public void setEventsDay(Collection eventsDay) {
        this.eventsDay = eventsDay;
    }

      
    public ScheduleUserBean getService() {
        return service;
    }

    public void setService(ScheduleUserBean service) {
        this.service = service;
    }

    public ScheduleEvent getEvent() {
        return event;
    }
 
    public void setEvent(ScheduleEvent event) {
        this.event = event;
    }
    public List<Event> getExistingUploadedEventsOverlap() {
        return existingUploadedEventsOverlap;
    }

    public void setExistingUploadedEventsOverlap(List<Event> existingUploadedEventsOverlap) {
        this.existingUploadedEventsOverlap = existingUploadedEventsOverlap;
    }

    public EventBean getBean() {
        return bean;
    }

    public void setBean(EventBean bean) {
        this.bean = bean;
    }
   
//    public ScheduleModel getEventModel() {
//        return eventModel;
//    }
//
//    public void setEventModel(ScheduleModel eventModel) {
//        this.eventModel = eventModel;
//    }
//    
    public UploadedFile getFileUploaded() {
        return fileUploaded;
    }

    public void setFileUploaded(UploadedFile fileUploaded) {
        this.fileUploaded = fileUploaded;
    }
    
    public void advancedSearch(){
        
    }
    
    public ScheduleModel retrieveEventModel(){
        //Obtain all events of the user
        Collection events = em.getAllEvents(um.getLoggedUser());
        Collection<Event> eventsCopy = new ArrayList<>();
        eventsCopy.addAll(events);
        //Add the events to eventModel
        return addEventToEventModel(eventsCopy); 
    }
    
    public Collection<Event> nextEvents(){
        
        Collection<Event> nextEvents = em.getAllEvents(um.getLoggedUser());
        List<Event> result = next(nextEvents);
        Collections.sort(result,new Comparator<Event>() {

            @Override
            public int compare(Event o1, Event o2) {
                
                if(o1.getDateBegin().before(o2.getDateBegin()))
                    return -1;
                else
                    return 1;
            }
        });
        return result;
    }
    
    private List<Event> next(Collection<Event> eventList){
        List<Event> result = new ArrayList<>();
        int i=0;
        for(Event e: eventList){
            if(e.getDateBegin().after(getToday())){
                result.add(e);
                i++;
            }
            if(i==5) break;
        }
            return result;
    }
    
    private Date getToday() {
        java.util.Calendar c = java.util.Calendar.getInstance();
        c.set(java.util.Calendar.HOUR_OF_DAY, 0);
        c.set(java.util.Calendar.MINUTE, 0);
        c.set(java.util.Calendar.SECOND, 0);
        c.set(java.util.Calendar.MILLISECOND, 0);
        return c.getTime();
    }
    
    public void upload() throws IOException, FileNotFoundException, ParserException {
        if(fileUploaded != null) {
                try{
                    saveUploadedFile();
                    //Obtain all events of the user
                    List<Event> allEvents = um.cloneL(em.getAllEvents(um.getLoggedUser()));

                    //Obtain events from uploaded file. The events already in the user schedule are discarded
                 List<VEvent> importedEvents = eventFromUploadedCalendarNotInSchedule(allEvents);
                    //Remove events not existing in application. For the other are controlled the overlapping
                    //If there is overlapping an event is inserted in existingEventsOverlap
                    //If There is not overlapping the user is added as a participant
                    existingUploadedEventsOverlap = manageEvents(importedEvents);
                    FacesMessage message = new FacesMessage("Import complete!",(importedEvents.size()-(existingUploadedEventsOverlap.size()/2)) + " events correctly uploaded\n" + (existingUploadedEventsOverlap.size()/2) + " events discarded for overlapping.");
                    FacesContext.getCurrentInstance().addMessage(null, message);
                } catch(IOException | ParserException e){
                    FacesMessage message = new FacesMessage("Error!","Sorry! The file should be a valid calendar");
                    FacesContext.getCurrentInstance().addMessage(null, message);
                }
//            }
        }
    }

    private List<Event> manageEvents(List<VEvent> uploadedList){
        //List of events overlapping with uploaded events. 
        List<Event> events = new ArrayList<>();
        
        for(VEvent item: uploadedList){
            Event app = em.overlapping(um.getLoggedUser(),this.convert(item).setHoursInDate());
            if(app==null){
                   em.createEvent(convert(item),um.getLoggedUser()); 
            }
            else{
                events.add(app);
                events.add(this.convert(item));
            }
        }
        return events;
    }
    
    private Event convert (VEvent e){
        String str[] = e.getLocation().getValue().split(", ");
       
        String description = "";
        Boolean public1 = false;
        String substrstr = e.getDescription().getValue().substring(9);
        Boolean outdoor = substrstr.startsWith("Yes");
        if(outdoor | substrstr.startsWith("No")){
            int substr = 20;
                    if(outdoor) substr += 1;

            String substrstr2 = e.getDescription().getValue().substring(substr);   
            public1 = substrstr2.startsWith("Yes");
            substr += 3;
                    if(public1) substr += 1;

            description = e.getDescription().getValue().substring(substr);
        } else{
            description = e.getDescription().getValue();
        }
            
        Date datebeg = convertDate(e.getStartDate().getDate());
        Date dateend = convertDate(e.getEndDate().getDate());
        Date hourbeg = convertDate(e.getStartDate().getDate());
        Date hourend = convertDate(e.getEndDate().getDate());
        Event result = new Event(e.getSummary().getValue(), str[0], str[1], description ,datebeg, dateend, hourbeg, hourend, public1,outdoor);
        return result;
    }
    
    private java.util.Date convertDate(net.fortuna.ical4j.model.Date date){
        return new java.util.Date(date.getTime());
    } 
    
    private List<VEvent> eventFromUploadedCalendarNotInSchedule(List<Event> allEvents) throws IOException, FileNotFoundException, ParserException{
        Calendar calendar = calendarFromFile();
        List<VEvent> result = new ArrayList<>();
        for (Iterator<Component> i = calendar.getComponents().iterator(); i.hasNext(); ) {
            Component component=i.next();
            if (component.getName().equals("VEVENT")) {
                VEvent ev=(VEvent)component;
                //if(!contains(allEvents,ev)){
                    result.add(ev);
                //}
            }
        }
        return result;
    }
    
    private Boolean contains (List<Event> list, VEvent item){
        
        Uid id = item.getUid();
        Integer val = Integer.parseInt(id.getValue());
        
        for(Event it: list){
            if((val.compareTo(it.getIdEvent())) == 0){
                return true;
            }
        }
        return false;
    }
    
    private void saveUploadedFile(){
        pathImport = "./meteocalUploaded.ics"; 
        OutputStream out;
                try { //--- getters and setters
                    InputStream in = fileUploaded.getInputstream();
                    out = new FileOutputStream(new File(pathImport));
                    int read;
                    byte[] bytes = new byte[1024];
                    while ((read = in.read(bytes)) != -1) {
                        out.write(bytes, 0, read);
                    }     
                    out.flush();
                    out.close();
                } catch (IOException ex) {
                    Logger.getLogger(ScheduleBean.class.getName()).log(Level.SEVERE, null, ex);
                }
    }
    
    
    
     public StreamedContent export() {
        StreamedContent file;
        try{
           file = new DefaultStreamedContent(new FileInputStream(pathExport), contentType, "meteocal.ics");
           return file;
        } catch (FileNotFoundException e){
            return null;
        }
        
    }

    public void createCalendarIcs() throws IOException, FileNotFoundException, ValidationException {
        
        //Set basic configuration of log4j
        BasicConfigurator.configure();
        try{
            Calendar calendar = createCalendar();
            List<Event> allEvents = um.cloneL(em.getAllEvents(um.getLoggedUser()));
            
            addEventsToCalendar(calendar,allEvents);
            contentType = "text/calendar";
            writeIntoFile(calendar);
            
        } catch (ValidationException e){
            FacesMessage message = new FacesMessage("Attention!","Error in export your calendar.");
            FacesContext.getCurrentInstance().addMessage(null, message);
        }

    }   
     

     
//    public void addEvent(ActionEvent actionEvent) {
//        if(event.getId() == null)
//            eventModel.addEvent(event);
//        else
//            eventModel.updateEvent(event);
//         
//        event = new DefaultScheduleEvent();
//    }
     
    public void onEventSelect(SelectEvent selectEvent) {
        event = (DefaultScheduleEvent) selectEvent.getObject();
    }
     
//    public void onDateSelect(SelectEvent selectEvent) {
//        Date date = (Date) selectEvent.getObject();
//        System.out.println("Date selected: " + date);
//        date.setHours(0);
//        date.setMinutes(0);
//        date.setSeconds(0);
//        
//       // this.eventsDay = getEventDay(eventModel.getEvents());
//        this.eventsDay = um.getEventDay(date);
//        System.out.println("Size===   " +eventsDay.size());
//    }
    
    public String gotoPage(){
        try{
            return bean.getEventDB(Integer.parseInt(event.getId()));
        } catch(NumberFormatException e){
            return "homepage?faces-redirect=true";
        }
    }
    
    private void addMessage(FacesMessage message) {
        FacesContext.getCurrentInstance().addMessage(null, message);
    }

    private ScheduleModel addEventToEventModel(Collection events) {
        try{
            ScheduleModel modelEvent = new DefaultScheduleModel();
            int i= 0;
            for(Object item: events){
                Event e = (Event) item;
                DefaultScheduleEvent d = new DefaultScheduleEvent(e.getName(), e.getDateBegin(), e.getDateEnd());
                String str = "";
                if(e.getAddress()!=null)
                    str=e.getAddress();
                d.setData(e.getDescription() + "\n" + str + "  (" + e.getCity() + ")");
                modelEvent.addEvent(d);
                modelEvent.getEvents().get(i).setId(e.getIdEvent().toString());
                i++;
            }
            return modelEvent;
        } catch(Exception e){
            return null;
        }
    }

    private Calendar createCalendar() {
        Calendar calendar = new Calendar();
        calendar.getProperties().add(new ProdId("-//Ben Fortuna//iCal4j 1.0//EN"));
        calendar.getProperties().add(Version.VERSION_2_0);
        calendar.getProperties().add(CalScale.GREGORIAN);
        
        return calendar;
    }
    
    private Calendar calendarFromFile() throws FileNotFoundException, IOException, ParserException {
        FileInputStream fin = new FileInputStream(pathImport);
        CalendarBuilder builder = new CalendarBuilder();
        return builder.build(fin);
    }

    private Boolean addEventsToCalendar(Calendar calendar, List<Event> events) throws SocketException {
        if(events == null || events.isEmpty())
            return false;
        for(Event item: events){
            VEvent e = createVEvent(item);
            calendar.getComponents().add(e);
        }
        return true;
    }

    private void writeIntoFile(Calendar calendar) throws FileNotFoundException, IOException, ValidationException {
        pathExport = "./meteocal.ics"+um.getLoggedUser().getEmail();

        FileOutputStream fout = new FileOutputStream(pathExport);

        CalendarOutputter outputter = new CalendarOutputter();
        outputter.output(calendar, fout);
    }

    private VEvent createVEvent(Event item) throws SocketException {
        
        //Convert Date begin and end
        net.fortuna.ical4j.model.DateTime datebeg = new net.fortuna.ical4j.model.DateTime(item.getDateBegin());
        net.fortuna.ical4j.model.DateTime dateend = new net.fortuna.ical4j.model.DateTime(item.getDateEnd());
        
        //Create a new VEvent
        VEvent e = new VEvent(datebeg,dateend,item.getName());
        
//        //Add IDEvent
//        Uid u = new Uid(item.getIdEvent().toString());
//        e.getProperties().add(u);
        
        UidGenerator ug = new UidGenerator("1");
        e.getProperties().add(ug.generateUid());

        
        //Set properties Date
        e.getProperties().getProperty(Property.DTSTART).getParameters().add(Value.DATE_TIME);
        e.getProperties().getProperty(Property.DTEND).getParameters().add(Value.DATE_TIME);
        
        //Set other fields
        e.getProperties().add(new Location(item.getAddress() + ", " + item.getCity()));
        
        String publicOutdoor = convertToString(item.getOutdoor(),item.getPublic1());
        e.getProperties().add(new Description(publicOutdoor + item.getDescription()));
        /*e.getProperties().add(new Organizer(item.getIdCreator().getEmail()));*/
        return e;     
    }

    private String convertToString(boolean outdoor, boolean public1) {
        String result = "Outdoor: ";
        if(outdoor)
            result += "Yes";
        else
            result += "No";
        
        result += "\nPublic: ";
        if(public1)
            result += "Yes";
        else
            result += "No";
        
        return result+"\n";
    }

    private Boolean overlapping(Event existingEvent, VEvent uploadedEvent) {
        
        Date edatebeg = existingEvent.getDateBegin();
        Date udatebeg = uploadedEvent.getStartDate().getDate();
        Date edateend = existingEvent.getDateEnd();
        Date udateend = uploadedEvent.getStartDate().getDate();
        if((edatebeg.after(udatebeg) && edatebeg.before(udateend)) ||
           (edateend.after(udatebeg) && edateend.before(udateend)) ||
            (edatebeg.before(udatebeg) && edateend.after(udateend)))
            return true;
        else
        return false;
    }
}